
Adélia Cruz
Neural Network Developer
TL;DR: Crawler do Crawlee frequentemente encontram barreiras de CAPTCHA. Integrar o CapSolver permite resolver reCAPTCHA, Turnstile e mais, mantendo seus fluxos de raspagem estáveis e automatizados.

Ao construir crawlers com Crawlee, encontrar CAPTCHA é quase inevitável — especialmente em sites modernos com proteção contra bots agressiva. Mesmo crawlers bem configurados com Playwright ou HTTP podem ser bloqueados assim que desafios como reCAPTCHA, Turnstile ou similares aparecerem.
Este guia se concentra em uma abordagem prática: usar o CapSolver para lidar com desafios de CAPTCHA diretamente nos fluxos do Crawlee. Em vez de lutar constantemente contra impressões digitais de navegador, você verá como detectar os tipos comuns de CAPTCHA, resolvê-los programaticamente e manter seus crawlers funcionando de forma confiável em cenários de raspagem do mundo real.
Crawlee é uma biblioteca de escaneamento de web e automação de navegador para Node.js, projetada para criar crawlers confiáveis que parecem humanos e passam despercebidos pela proteção contra bots moderna. Desenvolvido com TypeScript, ele fornece uma interface de alta simplicidade e personalização de baixo nível.
O Crawlee oferece vários tipos de crawler para diferentes casos de uso:
| Tipo de Crawler | Descrição |
|---|---|
| CheerioCrawler | Crawler HTTP ultra-rápido usando Cheerio para análise de HTML |
| PlaywrightCrawler | Automação de navegador completa com Playwright para sites com JavaScript pesado |
| PuppeteerCrawler | Automação de navegador completa com Puppeteer para renderização de JavaScript |
| JSDOMCrawler | Crawler HTTP com JSDOM para execução de JavaScript sem navegador |
CapSolver é um serviço líder de resolução de CAPTCHA que fornece soluções com inteligência artificial para contornar diversos desafios de CAPTCHA. Com suporte a vários tipos de CAPTCHA e tempos de resposta rápidos, o CapSolver se integra perfeitamente aos fluxos automatizados.
Ao construir crawlers do Crawlee que interagem com sites protegidos, desafios de CAPTCHA podem interromper todo o seu pipeline de raspagem. Aqui está o porquê da integração ser importante:
Primeiro, instale os pacotes necessários:
npm install crawlee playwright axios
Ou com yarn:
yarn add crawlee playwright axios
Aqui está uma classe de utilitário do CapSolver reutilizável que pode ser usada em seus projetos do Crawlee:
import axios from 'axios';
const CHAVE_API_CAPSOLVER = 'SUA_CHAVE_API_CAPSOLVER';
interface ResultadoTarefa {
status: string;
solução?: {
gRecaptchaResponse?: string;
token?: string;
};
descriçãoErro?: string;
}
class ServiçoCapSolver {
private chaveApi: string;
private baseUrl = 'https://api.capsolver.com';
constructor(chaveApi: string = CHAVE_API_CAPSOLVER) {
this.chaveApi = chaveApi;
}
async criarTarefa(dadosTarefa: object): Promise<string> {
const resposta = await axios.post(`${this.baseUrl}/criarTarefa`, {
clienteChave: this.chaveApi,
tarefa: dadosTarefa
});
if (resposta.data.erroId !== 0) {
throw new Error(`Erro do CapSolver: ${resposta.data.descriçãoErro}`);
}
return resposta.data.idTarefa;
}
async obterResultadoTarefa(idTarefa: string, tentativasMax = 60): Promise<ResultadoTarefa> {
for (let i = 0; i < tentativasMax; i++) {
await this.dormir(2000);
const resposta = await axios.post(`${this.baseUrl}/obterResultadoTarefa`, {
clienteChave: this.chaveApi,
idTarefa
});
if (resposta.data.status === 'pronto') {
return resposta.data;
}
if (resposta.data.status === 'falha') {
throw new Error(`Tarefa falhou: ${resposta.data.descriçãoErro}`);
}
}
throw new Error('Tempo esgotado esperando pela solução do CAPTCHA');
}
private dormir(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async resolverReCaptchaV2(urlSite: string, chaveSite: string): Promise<string> {
const idTarefa = await this.criarTarefa({
tipo: 'ReCaptchaV2TarefaSemProxy',
urlSite,
chaveSite
});
const resultado = await this.obterResultadoTarefa(idTarefa);
return resultado.solução?.gRecaptchaResponse || '';
}
async resolverReCaptchaV3(
urlSite: string,
chaveSite: string,
açãoPagina = 'submit'
): Promise<string> {
const idTarefa = await this.criarTarefa({
tipo: 'ReCaptchaV3TarefaSemProxy',
urlSite,
chaveSite,
açãoPagina
});
const resultado = await this.obterResultadoTarefa(idTarefa);
return resultado.solução?.gRecaptchaResponse || '';
}
async resolverTurnstile(urlSite: string, chaveSite: string): Promise<string> {
const idTarefa = await this.criarTarefa({
tipo: 'AntiTurnstileTarefaSemProxy',
urlSite,
chaveSite
});
const resultado = await this.obterResultadoTarefa(idTarefa);
return resultado.solução?.token || '';
}
}
export const capSolver = new ServiçoCapSolver();
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const CHAVE_SITE_RECAPTCHA = 'SUA_CHAVE_SITE';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Processando ${request.url}`);
// Verifique se a página tem reCAPTCHA
const temRecaptcha = await page.$('.g-recaptcha');
if (temRecaptcha) {
log.info('reCAPTCHA detectado, resolvendo...');
// Obtenha a chave do site da página
const chaveSite = await page.$eval(
'.g-recaptcha',
(el) => el.getAttribute('data-sitekey')
) || CHAVE_SITE_RECAPTCHA;
// Resolva o CAPTCHA
const token = await capSolver.resolverReCaptchaV2(request.url, chaveSite);
// Injete o token - o textarea está oculto, então usamos JavaScript
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, token: string) => {
el.style.display = 'block';
el.value = token;
}, token);
// Submeta o formulário
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
log.info('reCAPTCHA resolvido com sucesso!');
}
// Extraia dados após o CAPTCHA ser resolvido
const título = await page.title();
const conteúdo = await page.locator('body').innerText();
await Dataset.pushData({
título,
conteúdo: conteúdo.slice(0, 1000)
});
},
maxRequisiçõesPorCrawl: 50,
headless: true
});
await crawler.run(['https://exemplo.com/pagina-protegida']);
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Processando ${request.url}`);
// reCAPTCHA v3 é invisível, detecte pelo script
const scriptRecaptcha = await page.$('script[src*="recaptcha/api.js?render="]');
if (scriptRecaptcha) {
log.info('reCAPTCHA v3 detectado, resolvendo...');
// Extraia a chave do site do src do script
const srcScript = await scriptRecaptcha.getAttribute('src') || '';
const matchChaveSite = srcScript.match(/render=([^&]+)/);
const chaveSite = matchChaveSite ? matchChaveSite[1] : '';
if (chaveSite) {
// Resolva reCAPTCHA v3
const token = await capSolver.resolverReCaptchaV3(
request.url,
chaveSite,
'submit'
);
// Injete o token em um campo oculto usando JavaScript
await page.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
log.info('Token reCAPTCHA v3 injetado!');
}
}
// Continue com a submissão do formulário ou extração de dados
const título = await page.title();
const url = page.url();
await Dataset.pushData({ título, url });
}
});
await crawler.run(['https://exemplo.com/pagina-protegida-v3']);
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Processando ${request.url}`);
// Verifique se há widget do Turnstile
const temTurnstile = await page.$('.cf-turnstile');
if (temTurnstile) {
log.info('Cloudflare Turnstile detectado, resolvendo...');
// Obtenha a chave do site
const chaveSite = await page.$eval(
'.cf-turnstile',
(el) => el.getAttribute('data-sitekey')
);
if (chaveSite) {
// Resolva o Turnstile
const token = await capSolver.resolverTurnstile(request.url, chaveSite);
// Injete o token usando JavaScript (campo oculto)
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
// Submeta o formulário
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
log.info('Turnstile resolvido com sucesso!');
}
}
// Extraia dados
const título = await page.title();
const conteúdo = await page.locator('body').innerText();
await Dataset.pushData({
título,
conteúdo: conteúdo.slice(0, 500)
});
}
});
await crawler.run(['https://exemplo.com/pagina-protegida-turnstile']);
Aqui está um crawler avançado que detecta automaticamente e resolve diferentes tipos de CAPTCHA:
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
interface InformaçõesCaptcha {
tipo: 'recaptcha-v2' | 'recaptcha-v3' | 'turnstile' | 'nenhum';
chaveSite: string | null;
}
async function detectarCaptcha(página: any): Promise<InformaçõesCaptcha> {
// Verifica reCAPTCHA v2
const recaptchaV2 = await página.$('.g-recaptcha');
if (recaptchaV2) {
const chaveSite = await página.$eval('.g-recaptcha', (el: Element) =>
el.getAttribute('data-sitekey')
);
return { tipo: 'recaptcha-v2', chaveSite };
}
// Verifica reCAPTCHA v3
const scriptRecaptchaV3 = await página.$('script[src*="recaptcha/api.js?render="]');
if (scriptRecaptchaV3) {
const srcScript = await scriptRecaptchaV3.getAttribute('src') || '';
const match = srcScript.match(/render=([^&]+)/);
const chaveSite = match ? match[1] : null;
return { tipo: 'recaptcha-v3', chaveSite };
}
// Verifica Turnstile
const turnstile = await página.$('.cf-turnstile');
if (turnstile) {
const chaveSite = await página.$eval('.cf-turnstile', (el: Element) =>
el.getAttribute('data-sitekey')
);
return { tipo: 'turnstile', chaveSite };
}
return { tipo: 'nenhum', chaveSite: null };
}
async function resolverCaptcha(
página: any,
url: string,
informaçõesCaptcha: InformaçõesCaptcha
): Promise<void> {
if (!informaçõesCaptcha.chaveSite || informaçõesCaptcha.tipo === 'nenhum') return;
let token: string;
switch (informaçõesCaptcha.tipo) {
case 'recaptcha-v2':
token = await capSolver.resolverReCaptchaV2(url, informaçõesCaptcha.chaveSite);
// Textarea oculta - use JavaScript para definir o valor
await página.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, t: string) => {
el.value = t;
}, token);
break;
case 'recaptcha-v3':
token = await capSolver.resolverReCaptchaV3(url, informaçõesCaptcha.chaveSite);
// Campo oculto - use JavaScript para definir o valor
await página.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
break;
case 'turnstile':
token = await capSolver.resolverTurnstile(url, informaçõesCaptcha.chaveSite);
// Campo oculto - use JavaScript para definir o valor
await página.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
break;
}
}
const crawler = new PlaywrightCrawler({
async requestHandler({ página, request, log, enqueueLinks }) {
log.info(`Processando ${request.url}`);
// Detecção automática de CAPTCHA
const informaçõesCaptcha = await detectarCaptcha(página);
if (informaçõesCaptcha.tipo !== 'nenhum') {
log.info(`Detectado ${informaçõesCaptcha.tipo}, resolvendo...`);
await resolverCaptcha(página, request.url, informaçõesCaptcha);
// Submeta o formulário se existir
const botãoEnviar = await página.$('button[type="submit"], input[type="submit"]');
if (botãoEnviar) {
await submitBtn.click();
await page.waitForLoadState('networkidle');
}
log.info('CAPTCHA resolvido com sucesso!');
}
// Extrair dados
const title = await page.title();
const url = page.url();
const text = await page.locator('body').innerText();
await Dataset.pushData({
title,
url,
text: text.slice(0, 1000)
});
// Continuar a navegação
await enqueueLinks();
},
maxRequestsPerCrawl: 100
});
await crawler.run(['https://example.com']);
Cada tipo de CAPTCHA requer um método diferente de submissão no contexto do navegador:
async function submitRecaptchaToken(page: any, token: string): Promise<void> {
// A área de texto de resposta está oculta - use JavaScript para definir o valor
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, token: string) => {
el.style.display = 'block';
el.value = token;
}, token);
// Defina o campo oculto se existir (comum em implementações personalizadas)
try {
await page.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
} catch (e) {
// O campo pode não existir
}
// Submeter o formulário
await page.click('form button[type="submit"]');
}
async function submitTurnstileToken(page: any, token: string): Promise<void> {
// Defina o token no campo oculto usando JavaScript
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
// Submeter o formulário
await page.click('form button[type="submit"]');
}
Para cenários em que você deseja resolução automática de CAPTCHA, pode carregar a extensão do CapSolver:
import { PlaywrightCrawler } from 'crawlee';
import path from 'path';
const crawler = new PlaywrightCrawler({
launchContext: {
launchOptions: {
// Carregar extensão do CapSolver
args: [
`--disable-extensions-except=${path.resolve('./capsolver-extension')}`,
`--load-extension=${path.resolve('./capsolver-extension')}`
],
headless: false // Extensões exigem modo não oculto
}
},
async requestHandler({ page, request, log }) {
log.info(`Processando ${request.url}`);
// A extensão resolverá automaticamente os CAPTCHAs
// Aguardar a resolução potencial de CAPTCHA
await page.waitForTimeout(5000);
// Continuar com a raspagem
const title = await page.title();
const content = await page.locator('body').innerText();
console.log({ title, content });
}
});
await crawler.run(['https://example.com/pagina-com-captcha']);
async function solveWithRetry(
solverFn: () => Promise<string>,
maxRetries = 3
): Promise<string> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await solverFn();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = Math.pow(2, attempt) * 1000; // Retorno exponencial
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Máximo de tentativas excedido');
}
// Uso
const token = await solveWithRetry(() =>
capSolver.solveReCaptchaV2(url, siteKey)
);
import axios from 'axios';
async function checkBalance(apiKey: string): Promise<number> {
const response = await axios.post('https://api.capsolver.com/getBalance', {
clientKey: apiKey
});
return response.data.balance || 0;
}
// Verificar antes de iniciar o crawler
const balance = await checkBalance(CAPSOLVER_API_KEY);
if (balance < 1) {
console.warn('Saldo do CapSolver baixo! Por favor, recarregue.');
}
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
// Armazenar tokens resolvidos para combinações de domínio/chave
const tokenCache = new Map<string, { token: string; timestamp: number }>();
const TOKEN_TTL = 90000; // 90 segundos
async function getCachedToken(
url: string,
siteKey: string,
solverFn: () => Promise<string>
): Promise<string> {
const cacheKey = `${new URL(url).hostname}:${siteKey}`;
const cached = tokenCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < TOKEN_TTL) {
return cached.token;
}
const token = await solverFn();
tokenCache.set(cacheKey, { token, timestamp: Date.now() });
return token;
}
import { PlaywrightCrawler, ProxyConfiguration } from 'crawlee';
const proxyConfiguration = new ProxyConfiguration({
proxyUrls: [
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080'
]
});
const crawler = new PlaywrightCrawler({
proxyConfiguration,
async requestHandler({ page, request, log, proxyInfo }) {
log.info(`Usando proxy: ${proxyInfo?.url}`);
// Sua lógica de resolução de CAPTCHA e raspagem aqui
}
});
import { PlaywrightCrawler, Dataset, ProxyConfiguration } from 'crawlee';
import { capSolver } from './capsolver-service';
interface Product {
name: string;
price: string;
url: string;
image: string;
}
const proxyConfiguration = new ProxyConfiguration({
proxyUrls: ['http://user:pass@proxy.example.com:8080']
});
const crawler = new PlaywrightCrawler({
proxyConfiguration,
maxRequestsPerCrawl: 200,
maxConcurrency: 5,
async requestHandler({ page, request, log, enqueueLinks }) {
log.info(`Raspando: ${request.url}`);
// Verificar se há qualquer CAPTCHA
const hasRecaptcha = await page.$('.g-recaptcha');
const hasTurnstile = await page.$('.cf-turnstile');
if (hasRecaptcha) {
const siteKey = await page.$eval(
'.g-recaptcha',
(el) => el.getAttribute('data-sitekey')
);
if (siteKey) {
log.info('Resolvendo reCAPTCHA...');
const token = await capSolver.solveReCaptchaV2(request.url, siteKey);
// Injetar token usando JavaScript (elemento oculto)
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, t: string) => {
el.style.display = 'block';
el.value = t;
}, token);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
}
if (hasTurnstile) {
const siteKey = await page.$eval(
'.cf-turnstile',
(el) => el.getAttribute('data-sitekey')
);
if (siteKey) {
log.info('Resolvendo Turnstile...');
const token = await capSolver.solveTurnstile(request.url, siteKey);
// Injetar token usando JavaScript (elemento oculto)
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
}
// Extrair dados do produto usando localizadores do Playwright
const productCards = await page.locator('.product-card').all();
const products: Product[] = [];
for (const card of productCards) {
products.push({
name: await card.locator('.product-name').innerText().catch(() => ''),
price: await card.locator('.product-price').innerText().catch(() => ''),
url: await card.locator('a').getAttribute('href') || '',
image: await card.locator('img').getAttribute('src') || ''
});
}
if (products.length > 0) {
await Dataset.pushData(products);
log.info(`Extraídos ${products.length} produtos`);
}
// Enfileirar links de paginação e categorias
await enqueueLinks({
globs: ['**/produtos/**', '**/pagina/**', '**/categoria/**']
});
},
failedRequestHandler({ request, log }) {
log.error(`Requisição falhou: ${request.url}`);
}
});
// Iniciar a navegação
await crawler.run(['https://example-store.com/produtos']);
// Exportar resultados
const dataset = await Dataset.open();
await dataset.exportToCSV('produtos.csv');
console.log('Raspagem concluída! Resultados salvos em produtos.csv');
A integração do CapSolver com o Crawlee desbloqueia o potencial total da raspagem web para desenvolvedores de Node.js. Combinando a infraestrutura robusta de navegação do Crawlee com as capacidades líderes de resolução de CAPTCHA do CapSolver, você pode construir raspadores confiáveis que lidam até mesmo com os mecanismos mais desafiadores de proteção contra robôs.
Seja para construir pipelines de extração de dados, sistemas de monitoramento de preços ou ferramentas de agregação de conteúdo, a combinação Crawlee + CapSolver fornece a confiabilidade e escalabilidade necessárias para ambientes de produção.
Pronto para começar? Registre-se no CapSolver e use o código de bônus CRAWLEE para obter um bônus extra de 6% em cada recarga!
Crawlee é uma biblioteca de raspagem web e automação de navegadores para Node.js, projetada para construir crawlers confiáveis. Ele suporta tanto a raspagem baseada em HTTP (com Cheerio/JSDOM) quanto a automação completa do navegador (com Playwright/Puppeteer), e inclui recursos embutidos como rotação de proxy, gerenciamento de sessão e anti-bot stealth.
O CapSolver se integra ao Crawlee por meio de uma classe de serviço que encapsula a API do CapSolver. Dentro do manipulador de requisições do seu crawler, você pode detectar desafios de CAPTCHA e usar o CapSolver para resolvê-los, depois injetar os tokens de volta na página.
O CapSolver suporta uma ampla gama de tipos de CAPTCHA, incluindo reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, AWS WAF, GeeTest e muitos outros.
O CapSolver oferece preços competitivos com base no tipo e volume de CAPTCHAs resolvidos. Visite capsolver.com para detalhes de preços atuais. Use o código CRAWLEE para obter um bônus de 6% na primeira recarga.
Sim! O CapSolver fornece uma API REST que pode ser integrada a qualquer framework do Node.js, incluindo Express, Puppeteer standalone, Selenium e mais.
Sim, o Crawlee é de código aberto e licenciado sob a Apache 2.0. O framework é gratuito para uso, embora você possa incorrer em custos com serviços de proxy e serviços de resolução de CAPTCHA como o CapSolver.
A chave do site é geralmente encontrada na fonte HTML da página. Procure por:
data-sitekey no elemento .g-recaptchadata-sitekey no elemento .cf-turnstileAprenda arquitetura de raspagem web escalável em Rust com reqwest, scraper, raspagem assíncrona, raspagem de navegador headless, rotação de proxies e tratamento de CAPTCHA compatível.

Compare o Selenium vs Puppeteer para resolver CAPTCHA. Descubra benchmarks de desempenho, notas de estabilidade e como integrar o CapSolver para o máximo de sucesso.
